package com.lazyboyl.sharding.proxy.net.frontend;

import com.lazyboyl.sharding.proxy.backend.service.CurrencyService;
import com.lazyboyl.sharding.proxy.config.ErrorCode;
import com.lazyboyl.sharding.proxy.net.entity.ShardingLoginInfo;
import com.lazyboyl.sharding.proxy.net.proto.mysql.BinaryPacket;
import com.lazyboyl.sharding.proxy.net.proto.mysql.ErrorPacket;
import com.lazyboyl.sharding.proxy.net.proto.mysql.MySQLMessage;
import com.lazyboyl.sharding.proxy.net.proto.mysql.OkPacket;
import com.lazyboyl.sharding.proxy.net.proto.util.CharsetUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;

/**
 * @author linzef
 * @since 2021-06-30
 * 类描述： 与navicat连接的核心信息类
 */
public class FrontendConnection {

    private static final Logger logger = LoggerFactory.getLogger(FrontendConnection.class);

    /**
     * netty暴露出去的端口
     */
    private Integer port;

    /**
     * netty的boss线程数
     */
    private Integer bossThread;

    /**
     * netty的work线程数
     */
    private Integer workThread;

    /**
     * 连接shardingProxy的账号
     */
    private String shardingProxyUser;

    /**
     * 连接shardingProxy的密码
     */
    private String shardingProxyPass;

    /**
     * shardingProxy的库名
     */
    private String shardingProxyDatabaseName;

    /**
     * 当前执行到的时序ID
     */
    private long id;

    /**
     * 当前的编码方式
     */
    private String charset;

    /**
     * 连接的终端的地址
     */
    private String host;


    private int charsetIndex;

    /**
     * 通道对象
     */
    private ChannelHandlerContext ctx;

    /**
     * sql执行对象
     */
    private FrontendQueryHandler queryHandler;

    /**
     * 当前配置的sharding的相关信息
     */
    private ShardingLoginInfo shardingLoginInfo;

    /**
     * 执行sql的service
     */
    private CurrencyService currencyService;


    /**
     * 功能描述： 初始化数据库的表的信息
     *
     * @param bin
     */
    public void initDB(BinaryPacket bin) {
        MySQLMessage mm = new MySQLMessage(bin.data);
        // to skip the packet type
        mm.position(1);
        String db = mm.readString();
        // 检查schema是否已经设置
        if (shardingProxyDatabaseName != null) {
            if (shardingProxyDatabaseName.equals(db)) {
                writeOk();
            } else {
                writeErrMessage(ErrorCode.ER_DBACCESS_DENIED_ERROR, "Not allowed to change the database!");
            }
            return;
        }
        if (db == null) {
            writeErrMessage(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '" + db + "'");
            return;
        } else {
            this.shardingProxyDatabaseName = db;
            writeOk();
            return;
        }
    }

    /**
     * 功能描述： 执行sql语句，例如加载数据库，执行增删改查的sql语句等
     *
     * @param bin 二进制包对象
     */
    public void query(BinaryPacket bin) {
        if (queryHandler != null) {
            // 取得语句
            MySQLMessage mm = new MySQLMessage(bin.data);
            mm.position(1);
            String sql = null;
            try {
                sql = mm.readString("UTF-8");
            } catch (UnsupportedEncodingException e) {
                writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET, "Unknown charset '" + "UTF-8" + "'");
                return;
            }
            if (sql == null || sql.length() == 0) {
                writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL");
                return;
            }

            // 执行查询
            queryHandler.query(sql);
        } else {
            writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Query unsupported!");
        }
    }

    /**
     * 功能描述：保持心跳连接的返回
     */
    public void ping() {
        writeOk();
    }

    /**
     * 功能描述： 将成功的消息返回给到navicat
     */
    public void writeOk() {
        ByteBuf byteBuf = ctx.alloc().buffer(OkPacket.OK.length).writeBytes(OkPacket.OK);
        ctx.writeAndFlush(byteBuf);
    }

    /**
     * 关闭navicat连接
     */
    public void close() {
        logger.info("close frontedconnection,host:{},port:{}", host, port);
        ctx.close();
    }

    /**
     * 功能描述： 将错误的信息返回给到navicat
     *
     * @param errno
     * @param msg
     */
    public void writeErrMessage(int errno, String msg) {
        writeErrMessage((byte) 1, errno, msg);
    }

    /**
     * 功能描述： 将错误的消息返回给到navicat
     *
     * @param id
     * @param errno
     * @param msg
     */
    public void writeErrMessage(byte id, int errno, String msg) {
        ErrorPacket err = new ErrorPacket();
        err.packetId = id;
        err.errno = errno;
        err.message = encodeString(msg, "UTF-8");
        err.write(ctx);
    }

    private final static byte[] encodeString(String src, String charset) {
        if (src == null) {
            return null;
        }
        if (charset == null) {
            return src.getBytes();
        }
        try {
            return src.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            return src.getBytes();
        }
    }

    public int getCharsetIndex() {
        return charsetIndex;
    }

    public Boolean setCharsetIndex(int charsetIndex) {
        String charset = CharsetUtil.getCharset(charsetIndex);
        if (charset != null) {
            this.charset = charset;
            this.charsetIndex = charsetIndex;
            return true;
        } else {
            return false;
        }
    }

    public CurrencyService getCurrencyService() {
        return currencyService;
    }

    public void setCurrencyService(CurrencyService currencyService) {
        this.currencyService = currencyService;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getCharset() {
        return charset;
    }

    public boolean setCharset(String charset) {
        int ci = CharsetUtil.getIndex(charset);
        if (ci > 0) {
            this.charset = charset;
            this.charsetIndex = ci;
            return true;
        } else {
            return false;
        }
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public Integer getBossThread() {
        return bossThread;
    }

    public void setBossThread(Integer bossThread) {
        this.bossThread = bossThread;
    }

    public Integer getWorkThread() {
        return workThread;
    }

    public void setWorkThread(Integer workThread) {
        this.workThread = workThread;
    }

    public String getShardingProxyUser() {
        return shardingProxyUser;
    }

    public void setShardingProxyUser(String shardingProxyUser) {
        this.shardingProxyUser = shardingProxyUser;
    }

    public String getShardingProxyPass() {
        return shardingProxyPass;
    }

    public void setShardingProxyPass(String shardingProxyPass) {
        this.shardingProxyPass = shardingProxyPass;
    }

    public String getShardingProxyDatabaseName() {
        return shardingProxyDatabaseName;
    }

    public void setShardingProxyDatabaseName(String shardingProxyDatabaseName) {
        this.shardingProxyDatabaseName = shardingProxyDatabaseName;
    }

    public ChannelHandlerContext getCtx() {
        return ctx;
    }

    public void setCtx(ChannelHandlerContext ctx) {
        this.ctx = ctx;
    }

    public FrontendQueryHandler getQueryHandler() {
        return queryHandler;
    }

    public void setQueryHandler(FrontendQueryHandler queryHandler) {
        this.queryHandler = queryHandler;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public ShardingLoginInfo getShardingLoginInfo() {
        return shardingLoginInfo;
    }

    public void setShardingLoginInfo(ShardingLoginInfo shardingLoginInfo) {
        this.shardingLoginInfo = shardingLoginInfo;
    }
}
